#pragma once
#include <iostream>
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connection.hpp"

// Reactor反应堆模式
// 我们要写的是基于反应堆模式的epoll服务器
class Reactor
{
    const static int revs_num = 128;

private:
    // 判断连接是否存在的方法
    bool IsConnectionExistsHelper(int sockfd)
    {
        auto iter = _connections.find(sockfd);
        if (iter == _connections.end())
        {
            // 说明不存在
            return false;
        }
        else
        {
            // 说明存在
            return true;
        }
    }

    // 重载一下
    bool IsConnectionExists(const std::shared_ptr<Connection> &conn)
    {
        return IsConnectionExistsHelper(conn->GetSockFd());
    }

    bool IsConnectionExists(int sockfd)
    {
        return IsConnectionExistsHelper(sockfd);
    }

    // 判断连接是否为空的方法
    bool IsConnectionEmpty()
    {
        return _connections.empty();
    }

    // 循环一次的方法
    int LoopOnce(int timeout)
    {
        // 获取就绪的fd
        int n = _epoller_ptr->WaitEvents(_revs, revs_num, timeout);
        return n;
    }

    // 事件派发的方法
    //  Dispatcher任务派发器，形参为有多少个就绪事件
    void Dispatcher(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int sockfd = _revs[i].data.fd;      // 就绪的fd
            uint32_t revents = _revs[i].events; // 就绪的事件
            // 在看我们读写事件是否就绪前，还得看看该fd有无异常以及连接是否被对端关闭
            // 但是在这里我们不想直接使用这么多的if语句中来做处理
            // if (revents & EPOLLERR)
            // {
            //     // 表明要处理异常
            // }
            // if (revents & EPOLLHUP)
            // {
            //     // 表明要处理连接关闭
            // }

            // 而是
            // 1.我们将所有的异常处理统一转化成IO错误
            // 2.然后所有的IO异常都统一转换成为一个异常处理函数
            // 这样我们就能把所有的异常处理都归到一个统一的异常处理函数中
            if (revents & EPOLLERR)
                // 统一转化成IO
                revents |= (EPOLLIN | EPOLLOUT); // 那么下面在读写时就会报错了

            if (revents & EPOLLHUP)
                // 统一转化成IO
                revents |= (EPOLLIN | EPOLLOUT); // 那么下面在读写时就会报错了

            // 那么我们只需要聚焦读写操作就行了
            if (revents & EPOLLIN)
            {
                // 表明该fd的读事件就绪
                // 1. 我们需不需要区分究竟是真的sockfd读事件就绪，还是是上面的异常呢？
                // 不需要区分，直接读就好了，因为读有问题就会出错，统一进行异常处理就好
                // （我们上面的操作就是为了这个的！！！）

                // 2. 我们还需要区分是listenfd有新连接还是普通fd的有数据可以读取了？
                // 不需要区分！！因为我们直接调用Connection中的读取方法就好了
                // 我们在Listener中是继承了基类Connection的
                // 直接重写一下Connection中的读取方法用来获取新连接就可以了

                // 我们先判断一下这个sockfd在_connections中存不存在
                //  然后直接找到该fd的Connection指针调用内部的读方法开始读
                //  这个时候就是我们的键值对映射发力了
                if (IsConnectionExists(sockfd))
                {
                    _connections[sockfd]->Recver();
                }
            }

            if (revents & EPOLLOUT)
            {
                // 表明该fd的写事件就绪
                // 和读事件一样
                if (IsConnectionExists(sockfd))
                {
                    _connections[sockfd]->Sender();
                }
            }
        }
    }

public:
    Reactor()
        : _epoller_ptr(std::make_unique<Epoller>()),
          _isrunning(false)
    {
    }

    // 运行服务器方法
    void Loop()
    {
        // 先判断一下是否有连接，没有自然是不需要启动的
        if (IsConnectionEmpty())
        {
            return;
        }

        _isrunning = true;
        int timeout = -1;
        while (_isrunning)
        {
            PrintConnection();
            int n = LoopOnce(timeout); // 先循环一次监听获取一下有多少个就绪事件
            Dispatcher(n);             // 然后传入事件派发器中去处理事件
        }
        _isrunning = false;
    }

    // 新增连接的方法
    // 该接口要把所有的新连接添加到_connections容器中，并且写透到epoll内核中
    void AddConnection(std::shared_ptr<Connection> &conn)
    {
        // 0. 不要重复添加
        if (IsConnectionExists(conn))
        {
            LOG(LogLevel::WARNING) << "conn is exists: " << conn->GetSockFd();
            return;
        }

        // 1.conn对应的fd和它要关心的事件写透到epoll内核中
        uint32_t events = conn->GetEvent();
        int sockfd = conn->GetSockFd();
        _epoller_ptr->AddEvent(sockfd, events);

        // 2.设置当前conn的拥有者回指指针
        conn->SetOwner(this);

        // 3.将具体的Connection对象添加到_connections容器内部
        _connections[sockfd] = conn;
    }

    // 开启对读写事件关心的方法
    void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
    {
        // 0.判断该fd的Connection连接是否存在
        if (!IsConnectionExists(sockfd))
        {
            // 该链接不存在
            LOG(LogLevel::WARNING) << "EnableReadWrite, conn is not exists";
            return;
        }

        // 1.修改当前sockfd对应的connection关心的事件
        uint32_t new_events = (EPOLLET | (enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0));
        _connections[sockfd]->SetEvent(new_events);

        // 2.写透到内核，调整sockfd对特定事件的关心
        _epoller_ptr->ModEvent(sockfd, new_events);
    }

    // 删除连接的方法
    void DelConnection(int sockfd)
    {
        // 1.epoll移除的时候，sockfd必须是合法的，也就是先要从epoll中移除该fd
        _epoller_ptr->DelEvent(sockfd);

        // 2.同时我们还有该fd和其映射Connection对象在_connections容器中
        // 先要将这一键值对从容器中移除
        _connections.erase(sockfd);

        // 3.最后再关闭异常的fd
        LOG(LogLevel::INFO) << "client quit: " << sockfd;
        close(sockfd);
    }

    // 停止服务器方法
    void Stop()
    {
        _isrunning = false;
    }

    // 输出所有合法fd
    void PrintConnection()
    {
        std::cout << "当前Reactor正在进行管理的fd List: ";
        for (auto &conn : _connections)
        {
            std::cout << conn.first << " ";
        }
        std::cout << std::endl;
    }

    ~Reactor() {}

private:
    // 1.epoll模型
    std::unique_ptr<Epoller> _epoller_ptr;

    // 2.是否启动
    bool _isrunning;

    // 3.管理所有的connection，本质是管理未来所有我获取到的fd
    // 我们将文件描述符加上其对应的接收和发送缓冲区封装成Connection类
    // 然后使用unordered_map进行映射其fd作为key，将各fd的Connection指针对象管理起来
    // fd : Connection
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;

    // 4.就绪的所有事件
    struct epoll_event _revs[revs_num];
};